import {
  ASSET_TYPES
} from 'shared/constants'
import {
  defineComputed,
  proxy
} from '../instance/state'
import {
  extend,
  validateComponentName
} from '../util/index'

export function initExtend(Vue) {
  Vue.cid = 0
  let cid = 1
  // 挂载extend方法
  Vue.extend = function (extendOptions) {
    // 容错处理
    extendOptions = extendOptions || {}
    // 缓存当前实例到Super
    const Super = this
    // 缓存当前实例cid
    const SuperId = Super.cid
    // 初始化cachedCtors
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // 如果参数的_Ctor属性上有当前实例的cid存在，直接返回cachedCtors[SuperId]
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    // 缓存name
    const name = extendOptions.name || Super.options.name
    // 初始化Sub
    const Sub = function VueComponent(options) {
      // 初始化
      this._init(options)
    }
    // 把当前对象原型赋值给Sub原型
    Sub.prototype = Object.create(Super.prototype)
    // 把Sub原型的构造函数只想Sub
    Sub.prototype.constructor = Sub
    // 给Sub添加cid属性
    Sub.cid = cid++
    // 合并策略，简单起见，直接用子覆盖父，特别注意：Vue不是这样实现的！！！
    Sub.options = Object.assign({}, Super.options, extendOptions)
    // Sub的super属性指向当前实例
    Sub['super'] = Super
    // 如果存在props属性，初始化props
    if (Sub.options.props) {
      initProps(Sub)
    }
    // 如果存在computed属性，初始化computed
    if (Sub.options.computed) {
      initComputed(Sub)
    }
    // 给Sub初始化extend属性
    Sub.extend = Super.extend
    // 给Sub初始化 components filters directives属性
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // 如果存在name属性
    if (name) {
      // 把Sub挂载到它自身components组件属性上
      Sub.options.components[name] = Sub
    }
    // 初始化 superOptions extendOptions sealedOptions属性
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    cachedCtors[SuperId] = Sub
    // 返回Sub构造函数
    return Sub
  }
}
// 初始化props
function initProps(Comp) {
  const props = Comp.options.props
  for (const key in props) {
    // 代理props
    proxy(Comp.prototype, `_props`, key)
  }
}
// 初始化computed
function initComputed(Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}